package 函数式编程.使用Stream;

/*
使用reduce

map()和filter()都是Stream的转换方法，而Stream.reduce()则是Stream的一个聚合方法，它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。

我们来看一个简单的聚合方法：
*/
/*
import java.util.stream.*;
public class 使用reduce {
    public static void main(String[] args) {
        //int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);
        Optional<Integer> sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce((x, y) -> x+y);
        System.out.println(sum.get()); // 45
    }
}
*/
/*
reduce()方法传入的对象是BinaryOperator接口，它定义了一个apply()方法，负责把上次累加的结果和本次的元素 进行运算，并返回累加的结果：

@FunctionalInterface
public interface BinaryOperator<T> {
    // Bi操作：两个输入，一个输出
    T apply(T t, T u);
}
上述代码看上去不好理解，但我们用for循环改写一下，就容易理解了：

Stream<Integer> stream = ...
int sum = 0;
for (n : stream) {
    sum = (sum, n) -> sum + n;
}
可见，reduce()操作首先初始化结果为指定值（这里是0），紧接着，reduce()对每个元素依次调用(acc, n) -> acc + n，其中，acc是上次计算的结果：

// 计算过程:
acc = 0 // 初始化为指定值
acc = acc + n = 0 + 1 = 1 // n = 1
acc = acc + n = 1 + 2 = 3 // n = 2
acc = acc + n = 3 + 3 = 6 // n = 3
acc = acc + n = 6 + 4 = 10 // n = 4
acc = acc + n = 10 + 5 = 15 // n = 5
acc = acc + n = 15 + 6 = 21 // n = 6
acc = acc + n = 21 + 7 = 28 // n = 7
acc = acc + n = 28 + 8 = 36 // n = 8
acc = acc + n = 36 + 9 = 45 // n = 9
因此，实际上这个reduce()操作是一个求和。

如果去掉初始值，我们会得到一个Optional<Integer>：

Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
if (opt.isPresent()) {
    System.out.println(opt.get());
}
这是因为Stream的元素有可能是0个，这样就没法调用reduce()的聚合函数了，因此返回Optional对象，需要进一步判断结果是否存在。

利用reduce()，我们可以把求和改成求积，代码也十分简单：

import java.util.stream.*;
public class 使用reduce {
    public static void main(String[] args) {
        int s = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(1, (acc, n) -> acc * n);
        System.out.println(s); // 362880
    }
}

注意：计算求积时，初始值必须设置为1。

除了可以对数值进行累积计算外，灵活运用reduce()也可以对Java对象进行操作。下面的代码演示了如何将配置文件的每一行配置通过map()和reduce()操作聚合
成一个Map<String, String>：
*/
/*
import java.util.*;
public class 使用reduce {
    public static void main(String[] args) {
        // 按行读取配置文件:
   	 List<String> props0 = List.of("profile+native", "debug+true", "logging+warn", "interval+500");
   	 String s="profile+nat=iv,e";
   	 String[] s1=s.split("\\=|\\+|\\,",4);
   	 System.out.println(Arrays.toString(s1));
        List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
        Map<String, String> map = props.stream()
                // 把k=v转换为Map[k]=v:
                .map(kv -> {
                    String[] ss = kv.split("\\=", 2);//以=为界，分成两组。
                    return Map.of(ss[0], ss[1]);
                })
                // 把所有Map聚合到一个Map:
                .reduce(new HashMap<String, String>(), (m, kv) -> {
               	 m.putAll(kv);
                   return m;}
                );
        // 打印结果:
        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
        });
    }
}
*/
/*
小结
reduce()方法将一个Stream的每个元素依次作用于BinaryOperator，并将结果合并。

reduce()是聚合方法，聚合方法会立刻对Stream进行计算。


reduce()方法详解#
Reduce 原意：减少，缩小
根据指定的计算模型将Stream中的值计算得到一个最终结果
解释：reduce 操作可以实现从Stream中生成一个值，其生成的值不是随意的，而是根据指定的计算模型。比如，之前提到count、min和max方法，因为常用而被纳入标准库中。
事实上，这些方法都是reduce操作。

reduce方法有三个override的方法：

Optional reduce(BinaryOperator accumulator);

T reduce(T identity, BinaryOperator accumulator);

<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);

公共集合#
测试代码中的所有集合，都是该集合。

    List<Person> javaProgrammers = new ArrayList<Person>() {
        {
            add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 2000, 18));
            add(new Person("Tamsen", "Brittany", "Java programmer", "female", 2371, 55));
            add(new Person("Floyd", "Donny", "Java programmer", "male", 3322, 25));
            add(new Person("Sindy", "Jonie", "Java programmer", "female", 35020, 15));
            add(new Person("Vere", "Hervey", "Java programmer", "male", 2272, 25));
            add(new Person("Maude", "Jaimie", "Java programmer", "female", 2057, 87));
            add(new Person("Shawn", "Randall", "Java programmer", "male", 3120, 99));
            add(new Person("Jayden", "Corrina", "Java programmer", "female", 345, 25));
            add(new Person("Palmer", "Dene", "Java programmer", "male", 3375, 14));
            add(new Person("Addison", "Pam", "Java programmer", "female", 3426, 20));
        }
    };

【方式一reduce(BinaryOperator accumulator)】

Optional<T> reduce(BinaryOperator<T> accumulator);
我们先看第一个变形，参数列表为一个函数接口BinaryOperator<T>,
BinaryOperator源码：
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
      public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}
看BinaryOperator接口源码，我们可以看到，它又继承了BiFunction<T,T,T>.
另外，在BinaryOperator接口中又定义了另个静态方法为minBy和maxBy,
上面我们提到BinaryOperator接口继承了BiFunction<T,T,T>，我们看一下BiFunction<T,T,T>源码：

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);//接收两个参数 t 和 u, 返回 R
}
Bifunction中有一个apply方法，接收两个参数，返回一个结果
小结： 不管是BinaryOperator类还是最终继承的BiFunction类，在类上都有@FunctionalInterface注解，因此reduce(BinaryOperator<T> accumulator)方法
需要一个函数式接口参数，该函数式接口需要两个参数，返回一个结果(reduce中返回的结果会作为下次累加器计算的第一个参数)，也就是累加器,最终得到一个Optional对象

测试示例代码：

    @Test
    public void Test() {
        int asInt = javaProgrammers.stream()
                                    .mapToInt(Person::getSalary)//返回数值流，减少拆箱封箱操作，避免占用内存  IntStream
                                    .reduce((x, y) -> x += y)// int
                                    .getAsInt(); //return int
        System.out.printf("方式一   reduce(BinaryOperator<T> accumulator)   求薪资测试结果："+asInt);

        //解析：
        //     1. reduce(BinaryOperator<T> accumulator)    reduce方法接受一个函数，这个函数有两个参数
        //     2. 第一个参数是上次函数执行的返回值（也称为中间结果），第二个参数是stream中的元素，这个函数把这两个值相加，得到的和会被赋值给下次执行这个函
        //        数的第一个参数
        
        //注意：
        //     1.第一次执行的时候第一个参数的值是Stream的第一个元素，第二个参数是Stream的第二个元素,如果只有一个参数，就不能使用？
        //     2.方法返回值类型是Optional
    }

【方式二reduce(T identity, BinaryOperator accumulator)】

T reduce(T identity, BinaryOperator<T> accumulator);
与第一种变形相同的是都会接受一个BinaryOperator函数接口，不同的是其会接受一个identity参数，identity参数与Stream中数据同类型，相当于一个的初始值，通过累加
器accumulator迭代计算Stream中的数据，得到一个跟Stream中数据相同类型的最终结果。
测试示例代码：
    @Test
    public void test1(){
        int reduce = javaProgrammers.stream().mapToInt(Person::getSalary).reduce(10000, (x, y) -> x += y);
        System.out.printf("方式二  reduce(T identity, BinaryOperator<T> accumulator)   求薪资测试结果："+reduce);

        //注意：与方式一相比设置了累加器的初始值，参数一（x）则不再是Stream中的第一个数据而是设置的初始值（10000）其他相同
    }
打印结果：
方式一 reduce(BinaryOperator<T> accumulator)   求薪资测试结果：57308
方式二 reduce(T identity, BinaryOperator<T> accumulator) 求薪资测试结果：67308 //初始值10000

【方式三 reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner)】

\<U\> U reduce(U identity,BiFunction\<U, ? super T, U\> accumulator,BinaryOperator\<U\> combiner);
我们先观察分析再次被改变的参数列表：
        1. 第一个参数：返回实例u，传递你要返回的U类型对象的初始化实例u

        2. 第二个参数：累加器accumulator，可以使用lambda表达式，声明你在u上累加你的数据来源t的逻辑，例如(u,t)->u.sum(t),此时lambda表达式的行参列表
           是返回实例u和遍历的集合元素t，函数体是在u上累加t

        3. 第三个参数：参数组合器combiner，接受lambda表达式。

根据参数我们一步一步分析代码示例：
*/

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
public class 使用reduce {
	//interface BiFunction<T, U, R>
	//interface BinaryOperator<T> extends BiFunction<T,T,T>
    @Test
    public void test2() {
        ArrayList<Integer> accResult_ = Stream.of(1, 2, 3, 4)
                //第一个参数，初始值为ArrayList
                .reduce(new ArrayList<Integer>(),
                        //第二个参数，实现了BiFunction函数式接口中apply方法，并且打印BiFunction
                        new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
                            @Override
                            public ArrayList<Integer> apply(ArrayList<Integer> acc, Integer item) {
                                acc.add(item);
                                System.out.println("item: " + item);
                                System.out.println("acc+ : " + acc);
                                System.out.println("BiFunction");
                                return acc;
                            }
                            //第三个参数---参数的数据类型必须为返回数据类型，改参数主要用于合并多个线程的result值
                            // （Stream是支持并发操作的，为了避免竞争，对于reduce线程都会有独立的result）
                        }, new BinaryOperator<ArrayList<Integer>>() {
                            @Override
                            public ArrayList<Integer> apply(ArrayList<Integer> acc, ArrayList<Integer> item) {
                                System.out.println("BinaryOperator");
                                acc.addAll(item);
                                System.out.println("item: " + item);
                                System.out.println("acc+ : " + acc);
                                System.out.println("--------");
                                return acc;
                            }
                        });
        System.out.println("accResult_: " + accResult_);

        System.out.println("------------------lambda优化代码-----------------");

        ArrayList<Integer> newList = new ArrayList<>();

        ArrayList<Integer> accResult_s = Stream.of(1,2,3,4)
                .reduce(newList,
                        (acc, item) -> {
                            acc.add(item);
                            System.out.println("item: " + item);
                            System.out.println("acc+ : " + acc);
                            System.out.println("BiFunction");
                            return acc;
                        }, (acc, item) -> null);
        System.out.println("accResult_s: " + accResult_s);
        
        //1.一参数
        List<Integer> intList = Arrays.asList(1,2,3);
        //Optional<Integer> result1=intList.stream().reduce(Integer::sum);
        Optional<Integer> result1=intList.stream().reduce((x,y)->x+y);//R apply(T t, U u);-->T apply(T t, T u);
        System.out.println(result1.get());//6
        
        //2.二参数
        Integer result2=intList.stream().reduce(100, Integer::sum);
        System.out.println(result2);//106
        //如果我们将stream改成parallelStream：
        Integer result3=intList.parallelStream().reduce(100, Integer::sum);
        System.out.println(result3);//306
        //为什么是306呢？因为在并行计算的时候，每个线程的初始累加值都是100，最后3个线程加出来的结果就是306。
        //并行计算和非并行计算的结果居然不一样，这肯定不是JDK的问题，我们再看一下JDK中对identity的说明：
        //identity必须是accumulator函数的一个identity，也就是说必须满足：对于所有的t,都必须满足 accumulator.apply(identity, t) == t
        //所以这里我们传入100是不对的，因为sum（100+1）！= 1。
        //这里sum方法的identity只能是0。
        //如果我们用0作为identity,则stream和parallelStream计算出的结果是一样的。这就是identity的真正意图。
        
        //3.三参数: <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
        //多了一个combiner，这个combiner用来合并多线程计算的结果。
        
    }
}

/*
    示例代码中，第一个参数是ArrayList,在第二个函数参数中打印了“BiFunction”，而在第三个参数接口中打印了函数接口中打印了”BinaryOperator“.看下面的打印结
    果，只打印了“BiFunction”，而没有打印”BinaryOperator“，也就是说第三个函数参数并没有执行。分析参数时我们知道了该变形可以返回任意类型的数据。
    对于第三个函数参数，为什么没有执行，而且其参数必须为返回的数据类型？这是因为Stream是支持并发操作的，为了避免竞争，对于reduce线程都会有独立
    的result，combiner的作用在于合并每个线程的result得到最终结果。这也说明了了第三个函数参数的数据类型必须为返回数据类型了。 java8 reduce方法中的第三个
    参数combiner有什么作用？
打印结果：

item: 1
acc+ : [1]
BiFunction
item: 2
acc+ : [1, 2]
BiFunction
item: 3
acc+ : [1, 2, 3]
BiFunction
item: 4
acc+ : [1, 2, 3, 4]
BiFunction
另外需要注意：因为第三个参数用来处理并发操作，如何处理数据的重复性，应多做考虑，否则会出现重复数据！
*/